package com.yygo.dao.hibernate;

import com.yygo.Constants;
import com.yygo.dao.LoanRequestDao;
import com.yygo.model.*;
import com.yygo.model.enums.Phase;
import com.yygo.model.enums.RequestStatus;
import com.yygo.util.DateUtil;
import com.yygo.util.StringUtil;
import org.displaytag.properties.SortOrderEnum;
import org.hibernate.Criteria;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Property;
import org.hibernate.criterion.Restrictions;
import org.springframework.stereotype.Repository;

import java.util.*;

/**
 * Created by tiansha on 2015/6/13.
 */
@Repository("loanRequestDao")
public class LoanRequestDaoHibernate extends GenericDaoHibernate<LoanRequest, Long> implements LoanRequestDao {

    public LoanRequestDaoHibernate() {
        super(LoanRequest.class);
    }

    @Override
    public LoanRequest loadWholeLoanRequest(Long id) {
        LoanRequest request = super.get(id);

        readLazyCollections(request);

        return request;
    }

    private void readLazyCollections(LoanRequest request) {
        List<Feedback> list = request.getFeedbackList();
        for (Feedback feedback : list) {
            feedback.getContent();
        }

        List<TalkRecord> talkRecords = request.getTalkRecordList();
        for (TalkRecord talkRecord : talkRecords) {
            talkRecord.getContent();
        }

        List<Activity> activities = request.getActivities();
        for (Activity activity : activities) {
            activity.getActivity();
        }

        List<Rule> rules = request.getRules();
        for (Rule rule : rules) {
            rule.getDescription();
            for (String number: rule.getNumbers()) {
                String temp = number;
            }
        }

        List<Attachment> attachments = request.getAttachmentList();
        for (Attachment attachment : attachments) {
            String name = attachment.getName();
        }

        /*List<Material> materials = request.getMaterialAudit().getMaterialList();
        for (Material material : materials) {
            String name = material.getName();
        }*/

        if (request.getLoanProduct()!=null) {
            for (String name : request.getLoanProduct().getLoanMaterialNames()) {

            }

            for (String name : request.getLoanProduct().getCompleteLoanMaterialNames()) {

            }
        }
    }

    @Override
    public LoanRequest getLoanRequest(Long id) {
        LoanRequest request = super.get(id);

        List<Attachment> attachments = request.getAttachmentList();
        for (Attachment attachment : attachments) {
            attachment.getFileName();
        }

        /*List<Material> materials = request.getMaterialAudit().getMaterialList();
        for (Material material : materials) {
            String name = material.getName();
        }*/

        if (request.getLoanProduct()!=null) {
            for (String name : request.getLoanProduct().getLoanMaterialNames()) {

            }

            for (String name : request.getLoanProduct().getCompleteLoanMaterialNames()) {

            }
        }

        return request;
    }

    @Override
    public List<Feedback> getFeedbacks(Long reqId) {
        LoanRequest request = super.get(reqId);
        List<Feedback> list = request.getFeedbackList();
        for (Feedback feedback : list) {
            feedback.getContent();
        }

        List<Attachment> attachments = request.getAttachmentList();
        for (Attachment attachment : attachments) {
            attachment.getFileName();
        }

        return list;
    }

    @Override
    public int getMyBondLoanRequestsCount(User user, Date date, String name, String salesman) throws IllegalAccessException {
        Criteria criteria;
        if (user.getBondCompany() == null) {
            throw new IllegalAccessException("user is not a bond user");
        }
        if(user.getBondCompany().isMasterProvider()){
            criteria = buildMasterPolicyRequestsCriteria(user, date, name, salesman);
        } else {
            criteria = buildBondPolicyRequestsCriteria(user, date, name, salesman);
        }
        criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
        criteria.setProjection(Projections.rowCount());
        return Integer.parseInt(criteria.uniqueResult().toString());
    }

    @Override
    public List<LoanRequest> getMyBondLoanRequests(User user, Date date, String name, String salesman, int firstResult, int maxResults, SortOrderEnum sortDirection, String sortCriterion) throws IllegalAccessException {
        Criteria criteria;
        if (user.getBondCompany() == null) {
            throw new IllegalAccessException("user is not a bond user");
        }
        if(user.getBondCompany().isMasterProvider()){
            criteria = buildMasterPolicyRequestsCriteria(user, date, name, salesman);
        } else {
            criteria = buildBondPolicyRequestsCriteria(user, date, name, salesman);
        }
        setPagination(criteria, firstResult, maxResults, sortDirection, sortCriterion);
        criteria.setProjection(Projections.distinct(Projections.property("id")));

        List<Long> ids = criteria.list();
        List<LoanRequest> requests = new ArrayList<>();
        for (Long id : ids) {
            requests.add(get(id));
        }
        return requests;
    }

    @Override
    public int getMyLoanRequestCount(String phase, User user) {
        Criteria criteria = buildMyRequestCriteria(phase, user);
        criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
        criteria.setProjection(Projections.rowCount());
        return Integer.parseInt(criteria.uniqueResult().toString());
    }


    @Override
    public List<LoanRequest> getMyLoanRequests(String phase, User user, int firstResult, int maxResults, SortOrderEnum sortDirection, String sortCriterion) {
        Criteria criteria = buildMyRequestCriteria(phase, user);

        setPagination(criteria, firstResult, maxResults, sortDirection, sortCriterion);

        criteria.setProjection(Projections.distinct(Projections.property("id")));
        //criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);

        List<Long> ids = criteria.list();
        List<LoanRequest> requests = new ArrayList<>();
        for (Long id : ids) {
            requests.add(get(id));
        }
        /*DetachedCriteria criteria = DetachedCriteria.forClass(LoanRequest.class);
        criteria.add(Restrictions.eq("status", requestStatus)).add(Restrictions.or(
                Restrictions.eq("dealerUser", user),
                Restrictions.eq("auditUser", user)
        ));

        List requests = getHibernateTemplate().findByCriteria(criteria,
                firstResult, maxResults);*/
        return requests;
    }

    @Override
    public int getLoanRequestCount(User user, String status, Date date, String number, String name) {
        Criteria criteria = buildSearchCriteria(user, status, date, number, name);
        criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
        criteria.setProjection(Projections.rowCount());
        return Integer.parseInt(criteria.uniqueResult().toString());
    }

    @Override
    public List<LoanRequest> searchLoanRequests(User user, String status, Date date, String number, String name, int firstResult, int maxResults, SortOrderEnum sortDirection, String sortCriterion) {
        Criteria criteria = buildSearchCriteria(user, status, date, number, name);
        setPagination(criteria, firstResult, maxResults, sortDirection, sortCriterion);

        criteria.setProjection(Projections.distinct(Projections.property("id")));
        //criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);

        List<Long> ids = criteria.list();
        List<LoanRequest> requests = new ArrayList<>();
        for (Long id : ids) {
            requests.add(get(id));
        }
        return requests;
    }

    @Override
    public int getLoanedToTransferRequestsCount(User user, Date date, String number, String name) {
        Criteria criteria = buildLoanedToTransferCriteria(user, date, number, name);
        criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
        criteria.setProjection(Projections.rowCount());
        return Integer.parseInt(criteria.uniqueResult().toString());
    }

    @Override
    public List<LoanRequest> getLoanedToTransferRequests(User user, Date date, String number, String name, int firstResult, int maxResults, SortOrderEnum sortDirection, String sortCriterion) {
        Criteria criteria = buildLoanedToTransferCriteria(user, date, number, name);
        setPagination(criteria, firstResult, maxResults, sortDirection, sortCriterion);

        criteria.setProjection(Projections.distinct(Projections.property("id")));
        //criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);

        List<Long> ids = criteria.list();
        List<LoanRequest> requests = new ArrayList<>();
        for (Long id : ids) {
            requests.add(get(id));
        }
        return requests;
    }

    private Criteria buildSearchCriteria(User user, String status, Date date, String number, String name) {
        Criteria criteria = getSession().createCriteria(LoanRequest.class);
        criteria.add(Restrictions.ge("requestDate", date));
        boolean isDealer = isDealer(user);
        boolean isAdmin = isAdmin(user);
        boolean isBank = isBank(user);
        boolean isBond = isBond(user);
        if (!status.equals("all")){
            RequestStatus requestStatus = RequestStatus.valueOf(status);
            criteria.add(Restrictions.eq("status", requestStatus));
        } else if (!isDealer && !isAdmin) {
            criteria.add(Restrictions.not(Restrictions.eq("status", RequestStatus.draft)));
            //criteria.add(Restrictions.not(Restrictions.eq("status", RequestStatus.processing_rule)));
        }

        if (!StringUtil.isBlankOrNull(number)) {
            criteria.add(Restrictions.eq("number", number));
        }
        if (!StringUtil.isBlankOrNull(name)) {
            criteria.createAlias("requestPerson", "requestPerson").add(Restrictions.eq("requestPerson.name", name));
        }

        if (isDealer) {
            criteria.add(Restrictions.eq("dealerUser", user));
        } else if (isBank) {
            Criteria bondCriteria = getSession().createCriteria(Bank.class).add(Restrictions.eq("bankUser", user));
            Bank bank = (Bank)bondCriteria.uniqueResult();
            criteria.createAlias("loanProduct", "loanProduct").add(Restrictions.eq("loanProduct.bank", bank));
        } else if (isBond) {
            Criteria bondCriteria = getSession().createCriteria(BondCompany.class).add(Restrictions.eq("bondUser", user));
            BondCompany bondCompany = (BondCompany) bondCriteria.uniqueResult();
            List<BondCompany> bonds = getAllBondDescendent(bondCompany, false);

            criteria.createAlias("dealer", "dealer").add(Restrictions.in("dealer.bond", bonds));
        }

        criteria.addOrder(Order.desc("requestDate"));
        return criteria;
    }

    private Criteria buildLoanedToTransferCriteria(User user, Date date, String number, String name) {
        Criteria criteria = getSession().createCriteria(LoanRequest.class);
        criteria.add(Restrictions.ge("requestDate", date));
        boolean isDealer = isDealer(user);
        boolean isAdmin = isAdmin(user);
        boolean isBank = isBank(user);
        boolean isBond = isBond(user);
        criteria.add(Restrictions.eq("canPrepay", true)).add(Restrictions.isNull("moneyTransferRecord"));

        if (!StringUtil.isBlankOrNull(number)) {
            criteria.add(Restrictions.eq("number", number));
        }
        if (!StringUtil.isBlankOrNull(name)) {
            criteria.createAlias("requestPerson", "requestPerson").add(Restrictions.eq("requestPerson.name", name));
        }

        Criteria bondCriteria = getSession().createCriteria(Bank.class).add(Restrictions.eq("bankUser", user));
        Bank bank = (Bank)bondCriteria.uniqueResult();
        criteria.createAlias("loanProduct", "loanProduct").add(Restrictions.eq("loanProduct.bank", bank));

        criteria.addOrder(Order.desc("requestDate"));
        return criteria;
    }

    private List<BondCompany> getAllBondDescendent(BondCompany ascendent, boolean requireSlave) {
        List<BondCompany> bonds = new ArrayList<>();
        addChildren(ascendent, bonds, requireSlave);
        return bonds;
    }

    private void addChildren(BondCompany bondCompany, List<BondCompany> bonds, boolean requireSlave) {
        if (!requireSlave || bondCompany.isSlaveProvider()) {
            bonds.add(bondCompany);
        }
        if (bondCompany.getChildren() != null) {
            for (BondCompany bond : bondCompany.getChildren()) {
                if (!requireSlave || bond.isSlaveProvider()) {
                    bonds.add(bond);
                }
                addChildren(bond, bonds, requireSlave);
            }
        }
    }

    private Criteria buildMyRequestCriteria(String phase, User user) {
        Criteria criteria = null;
        List<User> auditUsers = new ArrayList<>();
        auditUsers.add(user);
        if (!phase.equals("cancelled") && !phase.equals("denied")) {
            Phase requestPhase = Phase.valueOf(phase);

            if (requestPhase.equals(Phase.insurance_confirm_cover) /*|| requestPhase.equals(Phase.insurance_loan_audit)*/ || requestPhase.equals(Phase.insurance_confirm_policy)) {
                List<BondCompany> slaveBonds = getSlaveProviders(user);
                for (BondCompany bondCompany : slaveBonds) {
                    auditUsers.add(bondCompany.getBondUser());
                }
            }
            criteria = getSession().createCriteria(LoanRequest.class).add(Restrictions.eq("phase", requestPhase));
            criteria.add(Restrictions.or(
                    Restrictions.eq("dealerUser", user),
                    Restrictions.in("auditUser", auditUsers)
            ));
            criteria.addOrder(Order.desc("requestDate"));
        }
        else if (phase.equals("cancelled")){
            criteria = getSession().createCriteria(LoanRequest.class).add(
                    Restrictions.or(
                            Restrictions.eq("phase", Phase.loan_cancelled),
                            Restrictions.eq("phase", Phase.request_cancelled)
                    )
            );
            criteria.add(Restrictions.or(
                    Restrictions.eq("dealerUser", user),
                    Restrictions.in("auditUser", auditUsers)
            ));
            criteria.addOrder(Order.desc("requestDate"));
        }
        else if (phase.equals("denied")){
            criteria = getSession().createCriteria(LoanRequest.class).add(
                    Restrictions.or(
                            Restrictions.eq("phase", Phase.loan_denied),
                            Restrictions.eq("phase", Phase.request_denied)
                    )
            );
            criteria.add(Restrictions.or(
                    Restrictions.eq("dealerUser", user),
                    Restrictions.in("auditUser", auditUsers)
            ));
            criteria.addOrder(Order.desc("requestDate"));
        }
        return criteria;
    }


    // for bond
    private Criteria buildBondPolicyRequestsCriteria(User user, Date date, String name, String salesman) {
        Criteria criteria = getSession().createCriteria(LoanRequest.class).add(Restrictions.eq("phase", Phase.loan_approved));
        criteria.add(Restrictions.ge("loanDate", date));
        if (!StringUtil.isBlankOrNull(name)) {
            criteria.createAlias("requestPerson", "requestPerson").add(Restrictions.eq("requestPerson.name", name));
        }
        if (!StringUtil.isBlankOrNull(salesman)) {
            criteria.add(Restrictions.eq("salesman", salesman));
        }
        Criteria bondCriteria = getSession().createCriteria(BondCompany.class).add(Restrictions.eq("bondUser", user));
        BondCompany bondCompany = (BondCompany) bondCriteria.uniqueResult();
        List<BondCompany> bonds = getAllBondDescendent(bondCompany, true);
        criteria.createAlias("dealer", "dealer").add(Restrictions.in("dealer.bond", bonds));
        return criteria;
    }

    // for master
    private Criteria buildMasterPolicyRequestsCriteria(User user, Date date, String name, String salesman) {
        Criteria criteria = getSession().createCriteria(LoanRequest.class).add(Restrictions.eq("phase", Phase.loan_approved));
        criteria.add(Restrictions.ge("loanDate", date));
        if (!StringUtil.isBlankOrNull(name)) {
            criteria.createAlias("requestPerson", "requestPerson").add(Restrictions.eq("requestPerson.name", name));
        }
        if (!StringUtil.isBlankOrNull(salesman)) {
            criteria.add(Restrictions.eq("salesman", salesman));
        }

        List<BondCompany> slaveBonds = getSlaveProviders(user);
        criteria.createAlias("dealer", "dealer").add(Restrictions.in("dealer.bond", slaveBonds));
        return criteria;
    }

    private Criteria buildRuleRequestCriteria() {
        Criteria criteria = getSession().createCriteria(LoanRequest.class).add(Restrictions.eq("status", RequestStatus.processing_rule));
        criteria.addOrder(Order.asc("requestDate"));
        return criteria;
    }

    private void setPagination(Criteria criteria, int firstResult, int maxResults, SortOrderEnum sortDirection, String sortCriterion) {
        criteria.setFirstResult(firstResult);
        criteria.setFetchSize(maxResults);
        if (sortCriterion != null) {
            if (sortDirection.equals(SortOrderEnum.ASCENDING)) {
                criteria.addOrder(Order.asc(sortCriterion));
            }
            if (sortDirection.equals(SortOrderEnum.DESCENDING)) {
                criteria.addOrder(Order.desc(sortCriterion));
            }
        }
    }

    @Override
    public LoanRequest getNextForRuleProcessing() {
        Criteria criteria = buildRuleRequestCriteria();
        criteria.setFirstResult(0);
        criteria.setMaxResults(1);
        List<LoanRequest> list = criteria.list();
        int size = list.size();
        if (size == 0)
            return null;

        LoanRequest loanRequest = list.get(0);
        readLazyCollections(loanRequest);
        return loanRequest;
    }

    private Criteria buildSlaveCriteria() {
        List<Phase> slavePhases = new ArrayList<>();
        slavePhases.add(Phase.insurance_confirm_cover);
        //slavePhases.add(Phase.insurance_loan_audit);
        slavePhases.add(Phase.insurance_confirm_policy);
        Criteria criteria = getSession().createCriteria(LoanRequest.class).add(Restrictions.in("phase", slavePhases));
        return criteria;
    }

    @Override
    public Set<LoanRequest> getRequestsForSlave() {
        Criteria criteria = buildSlaveCriteria();
        List<LoanRequest> list = criteria.list();
        for (LoanRequest loanRequest : list) {
            readLazyCollections(loanRequest);
        }
        return new HashSet<>(list);
    }

    @Override
    public LoanRequest getLoanRequestByNumber(String number) {
        Criteria criteria = getSession().createCriteria(LoanRequest.class).add(Restrictions.eq("number", number));
        Object object = criteria.uniqueResult();
        if (object != null) {
            readLazyCollections((LoanRequest) object);
            return (LoanRequest) object;
        }
        return null;
    }

    @Override
    public int getRecentRequestCountByDealer(Dealer dealer, int days) {
        Criteria criteria = getSession().createCriteria(LoanRequest.class).add(Restrictions.eq("dealer", dealer))
                .add(Restrictions.not(Restrictions.eq("status", RequestStatus.draft)));
        Property requestDate = Property.forName("requestDate");
        Date before = DateUtil.getDateBefore(days);
        criteria.add(requestDate.ge(before));

        criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
        criteria.setProjection(Projections.rowCount());

        return Integer.parseInt(criteria.uniqueResult().toString());
    }

    @Override
    public int getRecentDeniedRequestCountByDealer(Dealer dealer, int days) {
        Criteria criteria = getSession().createCriteria(LoanRequest.class).add(Restrictions.eq("dealer", dealer));
        Property requestDate = Property.forName("requestDate");
        Date before = DateUtil.getDateBefore(days);
        criteria.add(requestDate.ge(before));

        RequestStatus[] statuses = new RequestStatus[] {RequestStatus.credit_denied, RequestStatus.request_denied, RequestStatus.loan_denied};
        criteria.add(Restrictions.in("status", statuses));

        criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
        criteria.setProjection(Projections.rowCount());

        return Integer.parseInt(criteria.uniqueResult().toString());
    }

    private boolean isDealer(User user) {
        return user != null && (user.hasRole(Constants.DEALER_USER_ROLE));
    }

    private boolean isAdmin(User user) {
        return user != null && user.hasRole(Constants.ADMIN_ROLE);
    }

    private boolean isBond(User user) {
        return user != null && user.hasRole(Constants.Bond_USER_ROLE);
    }

    private boolean isBank(User user) {
        return user != null && user.hasRole(Constants.Bank_USER_ROLE);
    }

    public LoanRequest saveLoanRequest(LoanRequest loanRequest) {
        getSession().saveOrUpdate(loanRequest);
        // necessary to throw a DataIntegrityViolation and catch it in UserManager
        getSession().flush();
        return loanRequest;
    }

    private List<BondCompany> getSlaveProviders(User user) {
        Criteria criteria = getSession().createCriteria(Dealer.class);
        criteria.add(Restrictions.eq("masterProvider", user.getBondCompany()));
        List<Dealer> dealers = criteria.list();
        List<BondCompany> slaveBonds = new ArrayList<>();
        for (Dealer dealer : dealers) {
            slaveBonds.add(dealer.getSlaveProvider());
        }
        return slaveBonds;
    }
}
